
/*
 * SmartInstaller.m: the heart of the Smart Installer application.
 *
 * Copyright (C) 1994, Yves Arrouye <Yves.Arrouye@imag.fr>
 *
 */
 
/*
 * The Smart Installer application and the Smart Package utility suite may be
 * copied and distributed freely, as long as an acknowledgement of the author's
 * work, with its name and address, is kept. The code may not be distributed if
 * it has been modified. Modified code propositions should be directed to the
 * author who will made them if necessary and redistribute the packages.
 *
 */

#import <ctype.h>
#import <stdio.h>
#import <string.h>

#import <sys/file.h>
#import <sys/param.h>

#import "SmartInstaller.h"

/*
 * The DiskName string in the info file
 *
 */
 
#define DISK_NAME	"SmartDiskName"
#define DISK_NAME_LEN	13

/*
 * Exit codes
 *
 */
 
enum exit_codes {
    EXIT_OK,			// No error
    
    EXIT_NO_INFO,		// No info file
    EXIT_NO_DISK_NAME,		// No disk name in info file
    EXIT_NO_MEDIA_LIST,		// Cannot get removeable media list
    EXIT_NO_SUCH_DISK,		// Cancel when asking for a disk
    EXIT_NO_EJECT,		// Cannot eject the disk
    
    EXIT_UNKNOWN		// Default exit code
};
 
@implementation SmartInstaller

+ (BOOL)isLocationRoot
{
    return NO;
}

- setOnRoot:(BOOL)flag
{
    if (flag || (!flag && ![[self class] isLocationRoot])) {
    	isOnRoot = flag;
    }
    
    return self;
}

- (BOOL)isOnRoot
{
    return isOnRoot;
}

- init
{
    [super init];
    
    isOnRoot = [[self class] isLocationRoot];
    
    return self;
}

- initPackage:(const char*)from andDestination:(const char*)to
{
    return [[self init] setPackage:from andDestination:to];
}

- setPackage:(const char*)from andDestination:(const char*)to
{
    char* cptr;			// Useful char pointer
    const char* ccptr;		// Useful const char pointer
    
    // Remember these ones
    
    fromDir = from;
    destDir = to;
    
    [self setOnRoot:!*destDir];
    
    // Now get the basename (we assume that the extension is .pkg)
    
    if (ccptr = strrchr(fromDir, '/')) {
    	++ccptr;
    } else {
    	ccptr = fromDir;
    }
    
    cptr = rootName;
    while (*ccptr && strcmp(ccptr, ".pkg")) {
    	*cptr++ = *ccptr++;
    }
    *cptr = '\0';

    return self;
}
- (int)obtainDisk:(char*)wantedDiskName number:(int)diskVolume
      from:(const char*)diskName
{
    BOOL ret;
    
    sprintf(wantedDiskName, diskName, diskVolume);

    for (;;) {
	char* medias;
	
	if ([[Application workspace]
		getMountedRemovableMedia:&medias]) {
	    char* aMedia = strtok(medias, "\t");
	    char* dMedia = NULL;
	    int diskNumber = 0;
	    
	    while (aMedia &&
		sscanf(aMedia, diskName, &diskNumber) > 0 &&
		diskNumber != diskVolume) {
		if (diskNumber && !dMedia) {
		    dMedia = aMedia;
		}
		aMedia = strtok(NULL, "\t");
	    }
	    
	    if (diskNumber == diskVolume) {
		break;
	    } else {
		active = [NXApp activeApp]; [NXApp activateSelf:YES];
		
	    	if (diskNumber == 0) {
		    ret = (NXRunLocalizedAlertPanel("Localizable",
			"Disk Request",
			"Please insert disk %s and click Continue (or click Cancel if the disk is unavailable).",
			"Continue", "Cancel", NULL,
			wantedDiskName + 1) != NX_ALERTDEFAULT);
			
		    [NXApp activate:active];
		    
		    if (ret) {
			return EXIT_NO_SUCH_DISK;
		    }
		} else {
		    for (;;) {
			if ([[Application workspace]
				unmountAndEjectDeviceAt:dMedia]) {
			    break;
			} else {
			    active = [NXApp activeApp]; [NXApp activateSelf:YES];
			    
			    ret = (NXRunLocalizedAlertPanel("Localizable",
				"Disk Problem",
				"Cannot eject disk %s. Click Retry to have another try (or click Cancel to end the installation).",
				"Retry", "Cancel", NULL, dMedia + 1) !=
				NX_ALERTDEFAULT);
				
			    [NXApp activate:active];
			    
			    if (ret) {
				return EXIT_NO_EJECT;
			    }
			}
		    }
		    
		    active = [NXApp activeApp]; [NXApp activateSelf:YES];
		    
		    ret = (NXRunLocalizedAlertPanel("Localizable",
			"Disk Request",
			"You have inserted disk %s. Please insert disk %s and click Continue (or click Cancel if the disk is unavailable).",
			"Continue", "Cancel", NULL, dMedia + 1,
			wantedDiskName + 1) != NX_ALERTDEFAULT);
			
		    [NXApp activate:active];
		    
		    if (ret) {
			return EXIT_NO_SUCH_DISK;
		    }
		}
	    }
	} else {
	    active = [NXApp activeApp]; [NXApp activateSelf:YES];
	    
	    NXRunLocalizedAlertPanel("Localizable",
		"Disk Problem",
		"Unable to find the name of the disks currently mounted. Please retry later...",
		"Quit", NULL, NULL);
		
	    [NXApp activate:active];
	    
	    return EXIT_NO_MEDIA_LIST;
	}
	
	[[Application workspace] mountNewRemovableMedia:&medias];
    }

    return EXIT_OK;
}

- (int)installMultipleGzipped:(const char*)compName
{
    char infoName[MAXPATHLEN + 1];
    FILE* info;				// Info file handle
    
    int exitCode = EXIT_OK;
    
    if (![NXBundle getPath:infoName forResource:rootName
    	ofType:(isOnRoot ? "info.smart" : "info")
    	inDirectory:fromDir withVersion:0] || !(info = fopen(infoName, "r"))) {
    	NXRunLocalizedAlertPanel("Localizable", "Package Problem",
	    "Cannot open package %s (There was an error reading the info file.)",
	    "Quit", NULL, NULL, fromDir);
	exitCode = EXIT_NO_INFO;
    } else {
    	char diskName[MAXPATHLEN + 1];

        char infoLine[512];
	char* line;
	
	// Look for the line with the disk name.
	
	while (line = fgets(infoLine, sizeof(infoLine) / sizeof(char), info)) {
	    if (!strncmp(infoLine, DISK_NAME, DISK_NAME_LEN)) {
	    	break;
	    }
	}
	
	fclose(info);
	
	// Get this name.
	
	if (line != NULL) {
	    for (line += DISK_NAME_LEN; isspace(*line); ++line) {
	    	;
	    }
	    
	    if (*line) {
	    	char* cptr = diskName;
		
		*cptr++ = '/';	// Assume the disk will be mounted there
		
		do {
		    *cptr++ = *line++;
		} while (*line && *line != '\n');
		
		if (*line == '\n') {
		    *cptr = '\0';
		}
	    } else {
	    	line = NULL;
	    }
	}
	
	if (line == NULL) {
	    NXRunLocalizedAlertPanel("Localizable", "Package Problem",
		"Cannot open package %s (There was an error reading the info file.)",
		"Quit", NULL, NULL, fromDir);
	    exitCode = EXIT_NO_DISK_NAME;
	} else {
	    char wantedDiskName[MAXPATHLEN + 1];	// The current disk
	    char compTmpName[MAXPATHLEN + 1];	// Building there (without ext)
	    
	    BOOL again = YES;		// Do we have another volume?
	    
	    int origDiskVolume = 0;	// Our current disk volume
	    int diskVolume = 1;		// First volume to ask for
	    
	    char cmdLine[1024];
	    
	    sprintf(cmdLine, "%s/%s.pkg", diskName, rootName);
	    sscanf(fromDir, cmdLine, &origDiskVolume);

	    sprintf(compTmpName, "/tmp/%s.tar", rootName);
	    
	    sprintf(cmdLine, "%s.Z", compTmpName);
	    unlink(cmdLine);
	    sprintf(cmdLine, "%s.gz", compTmpName);
	    unlink(cmdLine);
	    	    
	    while (again) {
		char cmdLine[1024];
		
		// We have the right to use sprintf here because NeXT's doc.
		// says that the only % is for the %d in the volume name.
		
		if (exitCode = [self obtainDisk:wantedDiskName 
		    number:diskVolume from:diskName]) {
		    return exitCode;
		}
		
		// Now! We shoud be in a situation where the correct disk is
		// mounted under slash. Now please get the gzipped part.
		
		sprintf(cmdLine, "/bin/cat \"%s/%s.pkg/%s.tar.gz.%d\" >>%s.gz",
		    wantedDiskName, rootName, rootName, diskVolume,
		    compTmpName);
		    
		if (exitCode = system(cmdLine)) {
		    break;
		} else {
		    char lastName[MAXPATHLEN + 1];
		    
		    sprintf(lastName, "%s/%s.pkg/.last",
		    	wantedDiskName, rootName);
		    again = access(lastName, F_OK);
		    
		    if (!again && diskVolume == origDiskVolume) {
		    	break;
		    }

		    // Eject the disk
		    
		    for (;;) {
		    	if ([[Application workspace]
			   	unmountAndEjectDeviceAt:wantedDiskName]) {
			    break;
			} else {
			    BOOL ret;
			    
			    active = [NXApp activeApp]; [NXApp activateSelf:YES];
			    
			    ret = (NXRunLocalizedAlertPanel("Localizable",
			    	"Disk Problem",
				"Cannot eject disk %s. Click Retry to have another try (or click Cancel to end the installation).",
				"Retry", "Cancel", NULL, wantedDiskName + 1) !=
				NX_ALERTDEFAULT);
			
			    [NXApp activate:active];
			    
			    if (ret) {
				return EXIT_NO_EJECT;
			    }
			}
		    }
		    
		    // Next disk, please!
		    
		    ++diskVolume;
		}
	    }
	    
	    // Now funzip the whole archive, and remove it after that
	    
	    sprintf(cmdLine,
	    	"%s/funzip %s.gz | compress -c >%s.Z; /bin/rm -rf %s.gz; /bin/rm -f /tmp/%s.info; /bin/ln -s \"%s\" /tmp/%s.info",
	    	[[NXBundle mainBundle] directory],
		compTmpName, compTmpName, compTmpName, infoName,
		rootName, rootName);
	    
	    if (!(exitCode = system(cmdLine))) {
	    
	    	// Get the original disk back
		
	    	exitCode = [self obtainDisk:wantedDiskName 
		    number:origDiskVolume from:diskName];
		
		if (isOnRoot) {
		    active = [NXApp activeApp]; [NXApp activateSelf:YES];
		
		    if (NXRunLocalizedAlertPanel("Localizable",
			"Smart Installer",
			"Smart installation done. Click Install to launch Installer and install the package (or click Quit if you want to quit now).",
			"Install", "Quit", NULL) != NX_ALERTDEFAULT) {
			return EXIT_OK;
		    }
		}
		
		[[Application workspace] openFile:fromDir];
	    }
	}
    }
    
    return exitCode;	// Be careful: some return hide above!
}

- (int)installPackage
{
    char compName[MAXPATHLEN + 1];
    
    int exitCode = EXIT_UNKNOWN;
    
    sprintf(compName, "%s/%s.gz", fromDir, rootName);
    if (access(compName, R_OK) == 0) {
    	char cmdLine[1024];
	
	sprintf(cmdLine, "%s/funzip %s | /usr/ucb/compress -c >/tmp/%s.tar.Z",
	    [[NXBundle mainBundle] directory], compName, rootName);
	exitCode = system(cmdLine);
    } else {
    	exitCode = [self installMultipleGzipped:compName];
    }

    return exitCode;
}

@end
